/*!
* JavaScript Library v0.1
* Lastupdatedby Louis Chen 06/Jan/2012 HKT
*
*/

/*****
* Hock Test for Android
*
*
*/
function runGetLocale(locale){
	Hsbc.mobile.fn.locale = locale;
	Hsbc.deferred.resolve();
}

String.prototype.startsWith = function(str) {return (this.match("^"+str)==str)};

function initWebtrends(wtSettings){
	var dcs = new Webtrends.dcs();
	  dcs.init( {
	          dcsid: wtSettings.dcs.dcsid,
	          domain:wtSettings.dcs.domain,
	          disablecookie:wtSettings.dcs.disablecookie
	  });
}

function runTrackWebtrends(wtSettings,locale,dcsuri){
	var args={"DCS.dcsuri" : dcsuri,"DCS.language" : locale};
	var wtArgs=wtSettings.args;
	if(wtArgs!=undefined && wtArgs!=null)
	{
  	for(var elementId in wtArgs)
    {
      args['DCS.'+elementId]=wtArgs[elementId];
    }
	}
	Webtrends.multiTrack({
      args: args
  });
}

(function(window, undefined) {
	if (typeof window.Hsbc == "undefined") {
		window.Hsbc = new Object();
	}
	if(Hsbc){
		//define a global deferred object
		Hsbc.deferred = $.Deferred();
		Hsbc.topics = {};
	}
	Hsbc.mobile = (function() {
		var hsbcMobile = function(options) {
			return new hsbcMobile.fn.init(options);
		};

		function reverseAnimation(animation) {
			var opposites = {
				'up': 'down',
				'down': 'up',
				'left': 'right',
				'right': 'left',
				'in': 'out',
				'out': 'in'
			};
			return opposites[animation] || animation;
		}
		/**
		* For app hook apis
		*
		*/
		hsbcMobile.app = {
			getLocale : function(dtb,callbackjs){
				location.href = 'hsbc://function=GetLocale&callbackjs=' + (callbackjs||"runGetLocale");
				dtb.promise();
			},
			toggleMenu : function(){
				location.href = "hsbc://function=TogglePostLogonMenu";
			},
			updateMenu : function(funcName,callbackFunc){
				location.href = "hsbc://function=UpdatePostLogonMenu&datajs=" + funcName+"&callbackjs="+callbackFunc;
			},
			updateBanner : function(imglink,onclickurl,callbackFunc){
				location.href = "hsbc://function=UpdateMenuBanner&imglink=" + imglink+"&onclickurl="+onclickurl+"&callbackjs="+callbackFunc;
			},
			backToApp : function(locale, id){
				id = id || "home";
				location.href = "hsbc://function=BackToApp&id=" + id + "&lang=" + locale;
			},
			proxy : function(datajs, callback, url, method){
				location.href = "hsbc://function=Proxy&datajs=" + datajs + "&callbackjs=" + callback + "&url=" + url + "&method=" + method;
			},
			pageTransition : function(path, fromRight2Left){
				fromRight2Left = fromRight2Left ? "LTR" : "RTL";
				location.href = 'hsbc://function=PageTransition&slide=' + fromRight2Left + '&url=' + path;
			},
			//setter
			setter : function(key,datajs,callbackjs){
				location.href = 'hsbc://function=Setter&key=' + key +'&datajs=' + datajs+ '&callbackjs=' + callbackjs;
			},
			getter : function(key, callbackjs){
				location.href = 'hsbc://function=Getter&key=' + key + '&callbackjs=' + callbackjs;
			},
			/**start Mobile1.5 Premier Pack 1**/
			volatileGetter : function(key, callbackjs){
				location.href = 'hsbc://function=VolatileGetter&key=' + key + '&callbackjs='+ callbackjs;
			},
			volatileSetter : function(key, datajs , callbackjs){
				location.href = 'hsbc://function=VolatileSetter&key=' + key + '&datajs=' + datajs + '&callbackjs=' + callbackjs;
			},
			/**end Mobile1.5 Premier Pack 1**/
			getCookie : function(key, url, callbackjs){
				location.href = 'hsbc://function=GetCookies&url=' + url + '&key=' + key + '&callbackjs=' + callbackjs;
			},
			setCookie : function(key, url,datajs, callbackjs){
				location.href = 'hsbc://function=SetCookies&url=' + url + '&key=' + key +'&datajs='+datajs+'&callbackjs=' + callbackjs;
			},
			openInApp : function(url,customerDataType){
				location.href = 'hsbc://function=OpenInApp&needCustomStyle=' + customerDataType + '&url=' + url;
			},
			openInBrowser : function(url){
				location.href = 'hsbc://function=OpenInBrowser&url=' + url;
			},
			/** Native **/
			openAbout : function() {
				location.href = 'hsbc://function=showAboutToNative';
			},
			openBuzz : function(userinfo) {
				location.href = 'hsbc://function=SoundWaveToNative&userInfo=' + JSON.stringify(userinfo);
			}
		};
		hsbcMobile.fn = hsbcMobile.prototype = {
			opt: {
				tapBuffer: 100, // High click delay = ~350, quickest animation (slide) = 250
				isBrowser : false,
				rdirect : false // make the layout from right to left, left 2 right by default
			},
			currentPage : null,
			i18n: null,
			locale: 'en',
			hist: [],
			pages : null,
			index : -1, //for the page index to show the offset position div under document, high piority
			loader : null,
			init: function(options) {
				this._debug('Hsbc mobile init');
				this.opt = $.extend(this.opt, options);
				if(this.opt.wtSettings!=undefined)
				{
				   initWebtrends(this.opt.wtSettings);//init webtrends
		  	}
				if (this.opt.locale) {
					this.locale = this.opt.locale;
				}
				var that = this;
				var hash = location.search, index = 0;
				if($.trim(hash)){
					hash = hash.slice(1).split('&');
					var i = 0, tmp;
					for(; i < hash.length; i++){
						tmp = hash[i].split('=');
						if(tmp[0] == 'afp'){
							index = tmp[1] - 0;
							break;
						}
					}
				}
				if(that.opt.rdirect){
					$('body > div[role=page]').addClass('r-direct');
				}
				var pages = $('body > div[role=page]');
				pages.addClass('hidden');
				if(that.index != -1){
					index = that.index;
				}
				var currentPage = $(pages[index]);
				//currentPage.removeClass('hidden');
				that.addToHist(currentPage);
				//make sure works for two ways
				that.currentPage = currentPage;
				Hsbc.mobile.fn.currentPage = currentPage;
				that.adjust();
				that.pages = pages;
				pages = null;
				currentPage = null;
				that = null;
				return this;
			},
			adjust : function(){
				var u = navigator.userAgent;
				if(u.match(/OS [1-4]_/)||/android\s*[1-3]\./gi.test(u)){
					//do nothing here
			      $(document.createElement("style")).attr({type:"text/css",media:"screen"}).html('div[role="page"]{overflow-x:hidden !important}').appendTo("head");
	         }else{
					if(!arguments[0]){
						$(document.createElement("style")).attr({type:"text/css",media:"screen"}).html('body > * {height : 100%;} div[role="content"]{position:relative; -webkit-box-flex:1;box-flex:1;overflow-y : scroll; -webkit-overflow-scrolling : touch; -webkit-margin-collapse : separate;}').appendTo("head");
						if(!/android/gi.test(u)){
							$(document.createElement("style")).attr({type:"text/css",media:"screen"}).html('div[role="content"] > *{-webkit-transform : translate3d(0,0,0);}').appendTo("head");
						}
					}					
				}
			},
			setLocale: function(locale) {
				this._debug('setLocale');
				this.locale = locale;
			},
			getLocale: function(callback,funName) {
				var that = this;
				this._debug('getLocale');
				var localemapping=that.opt.localemapping;
				var locale=that.locale;
				if(localemapping!=undefined)
				{
					for(var localeKey in localemapping)
			    {
			    	if(locale==localeKey)
			    	{
			    		locale=localemapping[localeKey];
			    		break;
			    	}
			    }
			 }
				if(that.opt.isBrowser){
					callback(locale||'en');
					return false;
				}
				
				$.when(Hsbc.mobile.app.getLocale(Hsbc.deferred,funName));
				Hsbc.deferred.done(function(){
					callback(locale||'en');
				});
			},
			//revise if need ?
			//
			reqr: function(opts) {
				this._debug('reqr');
				if(!opts.baseUrl){
					//Default core library path
					opts.baseUrl = "../../../lib/js/";
				}
				var deferred = opts.deferred || false;
				if(opts.deps){
					if($.isArray(opts.deps)){
						//Must be array of strings
						$.each(opts.deps,function(i,name){
							opts.deps[i] = opts.baseUrl + this;
						});
					}
				}
				if($.isFunction(require)){
					require(opts.deps,function(){
						opts.callback.apply(window, arguments);
						if(deferred){
							deferred.resolve();
						}
					});
				}
				return (deferred ? deferred.promise() : false);
			},
			addToHist: function(page) {
				this._debug('addToHist');
				this.hist.unshift(page);
			},
			setNLS: function(data) {
				this._debug('setNLS');
				this.i18n = data;
			},
			/**
			* from				 the
			* to   		 	 the target page(jQuery Object)
			* keyframe    the name of webkit keyframe
			* goingBack 	 If true, using the opposite keyframe which defined in keyframe.
			*/
			anim: function(from, to, keyframe, goingBack) {
				this._debug('anim');
				keyframe = keyframe || (this.opt.rdirect ? 'slideright' : 'slideleft');
				var goingBack = goingBack ? goingBack: false, isAndroid2x = /android\s*[2-3]\./gi.test(navigator.userAgent),
				that = this;

				if (from.length == 0 || to.length == 0) {
					return false;
				}
				// Collapse the keyboard
				$(':focus').trigger('blur');
				// inactive the actived element
				// Kill the keyboard.
				// XXX_jblas: We need to stop crawling the entire document to kill focus. Instead,
				//            we should be tracking focus with a delegate() handler so we already have
				//            the element in hand at this point.
				// Wrap this in a try/catch block since IE9 throw "Unspecified error" if document.activeElement
				// is undefined when we are in an IFrame.
				try {
					if(document.activeElement && document.activeElement.nodeName.toLowerCase() != 'body') {
						$(document.activeElement).blur();
					} else {
						$( "input:focus, textarea:focus, select:focus" ).blur();
					}
				} catch(e) {}
					from.trigger('pageOut');
					to.trigger('pageIn');
					var finalAnimationName = keyframe;
					if (goingBack) {
						finalAnimationName = finalAnimationName.replace(/left|right|up|down|in|out/, reverseAnimation);
					}
					// Bind internal "cleanup" callback
					//from[0].addEventListener('webkitAnimationEnd',that,false);
					from.bind('webkitAnimationEnd', transitionEnd);
					//that.from = from;
					//that.to = to;
					//Check the target page is the first time?
					//if($.inArray(to,that.hist) == -1){
					//not exist
					that.tmpl(to, that.i18n);
					
					window.scrollTo(0,0);
					//}
					// #TODO Position the incoming page so toolbar is at top of viewport regardless of scroll position on from page
					to.removeClass('hidden').addClass(finalAnimationName + ' in');
					from.addClass(finalAnimationName + ' out');
					if(isAndroid2x){
						if(finalAnimationName.indexOf("slideleft")!=-1 || finalAnimationName.indexOf("slideright")!=-1){
							from.css({
								'webkitTransform' : 'translateX(' + (goingBack ? '-' : '') + window.innerWidth + 'px)'
							});
						}
						if(finalAnimationName.indexOf("slideup")!=-1 || finalAnimationName.indexOf("slidedown")!=-1){
							from.css({
								'webkitTransform' : 'translateY(' + (goingBack ? '-' : '') + window.innerHeight + 'px)'
							});
						}						
					}
					//internal function to handle
					function transitionEnd(event) {
						var bufferTime = that.opt.tapBuffer;
						from.unbind('webkitAnimationEnd', transitionEnd);
						//
						from.addClass('hidden').removeClass(finalAnimationName + ' out');
						to.removeClass(finalAnimationName + ' in');
						if(isAndroid2x){
							if(finalAnimationName.indexOf("slideleft")!=-1 || finalAnimationName.indexOf("slideright")!=-1){
								from.css('webkitTransform','');
							}
							if(finalAnimationName.indexOf("slideup")!=-1 || finalAnimationName.indexOf("slidedown")!=-1){
								from.css('webkitTransform','');
							}							
						}
						//add to Hist
						if (that.hist.length === 0) {
							that.addToHist(from);
						}
						if (goingBack) {
							//enhance the stack
							if(that.hist.length > 1){
								if(that.hist[1].attr('id') === to.attr('id')){
									that.hist.shift();
								}else{
									var index = -1;
									for(var i = 0; i< that.hist.length; i++){
										if(that.hist[i].attr('id') === to.attr('id')){
											index = i;
											break;
										}
									}
									//console.log("index========" + index);
									if(index > 1){
										index = index - 1;
										while(index >= 0){
											that.hist.splice(0,1);
											index--;
										}
									}
									//comment out
									//(that.hist.length > 0 && that.hist.shift());
								}
							}
						} else {
							that.addToHist(to);
						}
						//make sure works for two ways
						that.currentPage = to;
						Hsbc.mobile.fn.currentPage = to;
						var dcsuri=to.find("div[role=headbar]").attr('dcsuri');
						var fromPageDcsuri=from.find("div[role=headbar]").attr('dcsuri');
						if(fromPageDcsuri!=undefined && dcsuri!=undefined)
						{
							runTrackWebtrends(Hsbc.mobile.fn.opt.wtSettings,Hsbc.mobile.fn.locale,dcsuri);
					  }
					}
					return true;
				},
				goTo: function(opts) {
					this._debug('goTo');
					var that = this,
					opt = $.extend({
						'inPages': false,
						'from': that.hist[0],
						'keyframe': (that.opt.rdirect ? 'slideright' : 'slideleft')
					},
					opts || {});
					if (!opt.to) {
						return false;
					}
					if (opt.inPages) {
						//in pages
						Hsbc.mobile.app.pageTransition(opt.to,that.opt.rdirect);
					} else {
						//in page
						if (typeof opt.from === "string") {
							opt.from = $(opt.from);
						}
						if (typeof opt.to === "string") {
							opt.to = $(opt.to);
						}
						if(opt.from.attr('id')==opt.to.attr('id') )
						{
							return;
						}
						this.anim(opt.from, opt.to, opt.keyframe);
					}
				},
				goBack: function(opts) {
					this._debug('goBack');
					var len = this.hist.length,
					opt = $.extend({
						'inPages': false,
						'keyframe': (this.opt.rdirect ? 'slideright' : 'slideleft')
					},
					opts || {});
					if (len <= 1) {
						this.backToApp(this.locale,"home");
						return false;
					}
					if (opt.inPages) {
						//app hock
						Hsbc.mobile.app.pageTransition(opt.to,that.opt.rdirect?false:true);
					} else {
						//
						var from = this.hist[0],
						to = this.hist[1];
						if(opt.from){
							from = $(opt.from);
						}
						if(opt.to){
							to = $(opt.to);
						}
						this.anim(from, to, opt.keyframe, true);
					}
				},
				androidBack : function(opts){
					this._debug('androidBack');

					if(Hsbc.mobile.fn.currentPage.find("a.ui-back")[0] && Hsbc.mobile.fn.currentPage.find("a.ui-back").css('display')!='none'){
						 Hsbc.mobile.fn.currentPage.find("a.ui-back").click();
					}
					else
						{
							var currentId=Hsbc.mobile.fn.currentPage.attr('id');
							if(currentId !== undefined  && currentId !='' && currentId!='balances' && currentId.indexOf('logon')==-1)
							{
								Hsbc.mobile.fn.goTo({
									'inPages' : true,
									'to' : 'file:///web/banking/balance/balance.html'
								});
							}
						}
					},
					_debug : function(message){
						if (window.console !== undefined && this.opt.debug === true) {
							console.log(message);
						}
					},
					/**
					* To fix:
					* The scroll area is at the top and the user tries to scroll up
					* The scroll area is at the bottom and the user tries to scroll down.
					*
					*/
					_scrollFix : function(elem){
						this._debug('_scrollFix');
						if(!elem||!elem.nodeType) return;
						elem.addEventListener('touchstart',function(event){
							var startY = event.touches[0].pageY, startTopScroll = elem.scrollTop;
							if(startTopScroll <= 0){
								elem.scrollTop = 1;
							}
							if(startTopScroll + elem.offsetHeight >= elem.scrollHeight){
								elem.scrollTop = elem.scrollHeight - elem.offsetHeight - 1;
							}
						},false);
					},
					showLoadOverlay : function(text){
						var loader = $('body > div.ui-loader');
						if(!this.loader && loader.length == 0){
							this.loader = $('<div id="ui-loader" class="ui-loader"><div id="loading" class="loading defa ui-corner-all"><div class="loader"></div><div class="loadtext"></div></div></div>');
							$('body').append(this.loader);
						}else{
							this.loader = loader;
						}
						if(text!= undefined && text!='')
						{
							this.loader.find('.loadtext').html(text);
						}
						else
							this.loader.find('.loadtext').html("");
							loader = null;
							this.loader.css({
								height : $(document).height()
							}).show();
						},
						hideLoadOverlay : function(){
							(this.loader ? this.loader.hide() : $('body > div.ui-loader').hide());
						},
						/**
						* @param funcName String  /must be a global function name pass in
						*/
						updateMenu : function(funcName,callbackFunc){
							if(this.opt.isBrowser){
								return;
							}
							Hsbc.mobile.app.updateMenu(funcName,callbackFunc);
						},
						toggleMenu : function(){
							Hsbc.mobile.app.toggleMenu();
						},
						updateBanner : function(imglink,onclickurl,callbackFunc){
							if(this.opt.isBrowser){
								return;
							}
							Hsbc.mobile.app.updateBanner(imglink,onclickurl,callbackFunc);
					   },
						//This is just for testing
						proxy : function(datajs, callback, url, method){
							if(this.opt.isBrowser){
								callback();
							}else {
								Hsbc.mobile.app.proxy(datajs, callback, url, method);
							}
						},
						backToApp : function(locale,id){
							Hsbc.mobile.app.backToApp(locale,id);
						},
						setter : function(name,datajs,callbackjs){
							Hsbc.mobile.app.setter(name, datajs,callbackjs);
						},
						getter : function(name, callbackjs){
							Hsbc.mobile.app.getter(name, callbackjs);
						},
						//volatileGetter
						/**start Mobile1.5 Premier Pack 1**/
						volatileGetter : function(name,callbackjs){
							Hsbc.mobile.app.volatileGetter(name,callbackjs);
						},
						/**end Mobile1.5 Premier Pack 1**/
						//volatileSetter
						volatileSetter : function(name,datajs,callbackjs){
							Hsbc.mobile.app.volatileSetter(name,datajs,callbackjs);
						},
						getCookie : function(name, url, callbackjs){
							Hsbc.mobile.app.getCookie(name, url, callbackjs);
						},
						setCookie : function(name,url,datajs, callbackjs){
							Hsbc.mobile.app.setCookie(name, url,datajs, callbackjs);
						},
						openInApp : function(url,customerDataType){
							Hsbc.mobile.app.openInApp(url,customerDataType);
						},
						openInBrowser : function(url){
							Hsbc.mobile.app.openInBrowser(url);
						},
						initDatePicker:function(opts){
							this._debug("initDatePicker");
							var dps = opts.dps;
							var dates = [];
							$.datepicker.regional[this.locale] = {
								monthNames : dps.monthNames,
								closeText	:	dps.monthNames,
								prevText	: dps.prevText,
								nextText	: dps.nextText,
								currentText	: dps.currentText,
								monthNames	: dps.monthNames,
								monthNamesShort	: dps.monthNamesShort,
								dayNames	: dps.dayNames,
								dayNamesShort	: dps.dayNamesShort,
								dayNamesMin	: dps.dayNamesMin,
								weekHeader	: dps.weekHeader,
								firstDay	: dps.firstDay,
								isRTL	: dps.isRTL,
								showMonthAfterYear	: dps.showMonthAfterYear,
								yearSuffix	: dps.yearSuffix
							};
							$.datepicker.setDefaults($.datepicker.regional[this.locale]);
							return $("#"+opts.datePickerId).datepicker(
							{
								showButtonPanel: opts.showButtonPanel||false,
								showOtherMonths:opts.showOtherMonths||false,
								minDate: opts.minDate?opts.minDate:"",
								maxDate: opts.maxDate?opts.maxDate:"",
								defaultDate:opts.defaultDate?opts.defaultDate:"",
								onSelect: function(date){
									//Default Date
									dates[2] = date;
									var date = date.split("/");
									var year = date[2];
									var month = date[1];
									var day = date[0];
									//showDate
									dates[0] = dps.showDateFormat.replace(/yyyy/,year).replace(/yy/,year.slice(-2)).replace(/mm/,month).replace(/dd/,day);
									//submitDate
									dates[1] = dps.submitDateFormat.replace(/yyyy/,year).replace(/yy/,year.slice(-2)).replace(/mm/,month).replace(/dd/,day);
									if(opts.onSelect){
										opts.onSelect.apply(window,dates);
									}
								}
							});
						},
						/*,
						_transitionEnd : function(){
						fromPage.unbind('webkitAnimationEnd', navigationEndHandler);
						}*/

						//use to initial error message
						initError: function(path, errorFileName){
							var self = this;
							this.reqr({
								baseUrl : path,
								deps : [errorFileName + "_" + self.locale],
								callback : function(i18n){
									validateUtil.Error_numeric = i18n.Error_numeric;
									validateUtil.Error_blank = i18n.Error_blank;
									validateUtil.Error_inValid = i18n.Error_inValid;
									validateUtil.Error_blankSpace = i18n.Error_blankSpace;
									validateUtil.Error_limitNum = i18n.Error_limitNum;
									validateUtil.Error_mostNum = i18n.Error_mostNum;
									validateUtil.Error_alphanumeric = i18n.Error_alphanumeric;
									validateUtil.Error_alphabetic = i18n.Error_alphabetic;
									validateUtil.Error_secPassword = i18n.Error_secPassword;
									validateUtil.Error_equal = i18n.Error_equal;
									validateUtil.Error_notEqual = i18n.Error_notEqual;
									validateUtil.Error_textArea = i18n.Error_textArea;
									validateUtil.Error_small = i18n.Error_small;
									validateUtil.Error_great = i18n.Error_great;
									validateUtil.Error_early = i18n.Error_early;
									validateUtil.Error_earlyToday = i18n.Error_earlyToday;
									validateUtil.Error_inValidSeparator = i18n.Error_inValidSeparator;
									validateUtil.Error_inValidNumber = i18n.Error_inValidNumber;
									validateUtil.Error_positive = i18n.Error_positive;
									validateUtil.Error_phone = i18n.Error_phone;
									validateUtil.errHead = i18n.errHead;
									validateUtil.errFoot = i18n.errFoot;
									validateUtil.Error_adapt_field_type= i18n.Error_adapt_field_type;
									validateUtil.Error_adapt_not_instance= i18n.Error_adapt_not_instance;
									validateUtil.Error_adapt_invalid_property= i18n.Error_adapt_invalid_property;
									validateUtil.Error_adapt_required= i18n.Error_adapt_required;
									validateUtil.Error_adapt_pattern= i18n.Error_adapt_pattern;
									validateUtil.Error_adapt_maxlength= i18n.Error_adapt_maxlength;
									validateUtil.Error_adapt_minlength= i18n.Error_adapt_minlength;
									validateUtil.Error_adapt_minimum= i18n.Error_adapt_field_type;
									validateUtil.Error_adapt_maximum= i18n.Error_adapt_field_type;
									validateUtil.Error_adapt_enumeration= i18n.Error_adapt_enumeration;
									validateUtil.Error_adapt_digits= i18n.Error_adapt_digits;
									validateUtil.Error_adapt_object_required= i18n.Error_adapt_object_required;
									validateUtil.Error_adapt_additional= i18n.Error_adapt_additional;
									validateUtil.Error_adapt_presence= i18n.Error_adapt_presence;
								}
							});
						}


					};
					//
					// Give the init function the hsbcMobile prototype for later instantiation
					hsbcMobile.fn.init.prototype = hsbcMobile.fn;

					return hsbcMobile;
				})();
				/**
				* templates 0.0.1
				* Copyright (c) 2010-2011 Louis Y P Chen
				*
				* Lastupdatedby Louis Y P Chen 20/Mar/2012 HKT
				*/
				(function($) {
					var rendlines = /[\n\r]/g,
					Tmpl = function(elem, data) {
						if (typeof elem === "undefined") {
							return;
						}
						this._tree = [];
						this.elem = elem;
						var s = this.elem.html();
						if (!s) {
							return;
						}
						s = $.trim(s);
						if (!s) {
							return;
						}
						s = s.replace(/^<\!\[CDATA\[([\s\S]*)\]\]>$/im, '$1');
						this.elem.addClass('parse');
						return this.template(s, data);
					};
					$.extend(Tmpl.prototype, {
						version: '0.0.1',
						debug: false,
						clone_data: true,
						filter_data: true,
						template: function(s, data) {
							//remove endlines
							s = s.replace(rendlines, '');
							if (s.length > 0) {
								this._tree.push(new _node(s, this));
							}
							this.process(data);
							return this;
						},
						process: function(data) {
							var t = this;
							t.elem.html(t.get(data, t.elem, 0));
							//trigger the pageParsed event
							//This will be removed
							t.elem.trigger('pageParsed', {data : data});
							if(Hsbc.topics[t.elem.attr('id') + '/pageParsed']){
								Hsbc.topics[t.elem.attr('id') + '/pageParsed'].publish(data,t.elem);
							}
						},
						get: function(d, element, deep) {
							++deep;
							var $d = d;
							if (this.clone_data) {
								//Currently wo do not provide filter
								//#TODO ?!
								$d = this.cloneData(d, null, this.escapeHTML);
							}
							var ret = '';
							for (var i = 0, l = this._tree.length; i < l; i++) {
								ret += this._tree[i].get($d, this.elem, deep);
							}
							--deep;
							return ret;
						},
						escapeHTML: function(txt) {
							return txt.replace(/&/g, '&amp;').replace(/>/g, '&gt;').replace(/</g, '&lt;').replace(/"/g, '&quot;').replace(/'/g, '&#39;');
						},
						cloneData: function(d, filter, f_escapeString) {
							if (d == null) {
								return d;
							}
							switch (d.constructor) {
								case Object:
								var o = {};
								for (var p in d) {
									o[p] = this.cloneData(d[p], filter, f_escapeString);
								}
								return o;
								case Array:
								var o = [];
								for (var i = 0, l = d.lenght; i < l; i++) {
									o[i] = this.cloneData(d[i], filter, f_escapeString)
								}
								return o;
								case String:
								return f_escapeString(d);
								default:
								return d;
							}
						},
						evaluateContent: function(d, elem, expr) {
							if (expr.indexOf('${') == 0) {
								var p = expr.replace("${", "").replace("}", "").split(".")[1];
								//
								if(d[p] === undefined){
									return expr;
								}
								return d[p];
							}
						}
					});

					var _node = function(html, template) {
						this._html = html;
						this._template = template;
					};
					$.extend(_node.prototype, {
						get: function(data, element, deep) {
							var s = this._html;
							var rs = "",
							i = -1,
							nested = 0,
							sText = -1,
							sExpr = 0;
							while (true) {
								var lm = s.indexOf("${", i + 1);
								var rm = s.indexOf("}", i + 1);
								if (lm < 0 && rm < 0) {
									break;
								}
								if ((lm != -1 && lm < rm) || (rm == -1)) {
									i = lm;
									if (++nested == 1) {
										sText = lm;
										rs += s.substring(sExpr, i);
										sExpr = -1;
									}
								} else {
									i = rm;
									if (--nested === 0) {
										if (sText >= 0) {
											rs += this._template.evaluateContent(data, element, s.substring(sText, rm + 1));
											sText = -1;
											sExpr = i + 1;
										}
									} else if (nested < 0) {
										nested = 0;
									}
								}
							}
							if (sExpr > -1) {
								rs += s.substr(sExpr);
							}
							return rs;
						}
					});
					//assign
					Hsbc.mobile.fn.tmpl = function(elem, data) {
						var elem = (typeof(elem) === 'string') ? $(elem).eq(0) : (elem.target ? $(elem.target) : $(elem));
						if (elem.hasClass('parse')) {
							return false;
						}
						return new Tmpl(elem, data);
					}
				})(jQuery);
				/**
				* JSON validate v0.0.1
				* Lastupdatedby Louis Y P Chen 20/Mar/2012 HKT
				*/
				(function($,hsbc){
					//setup the primitive class for the types
					var privateT = {
						'string' : String,
						'boolean': Boolean,
						'number' : Number,
						'object' : Object,
						'array' : Array,
						'date': Date
					};
					//
					var adaptRegister = hsbc.mobile.fn.adaptRegister = function(/*JSON Object*/obj, /*Object*/adapt, /*Object*/options){
						options = options || {};
						var _changing = options.changing || false;
						//internal get type function
						function getType(adapt){
							return adapt.type || (privateT[adapt.name] === adapt && adapt.name.toLowerCase());
						}
						//To store the error(s)
						var errors = [];
						// validate a value against a property definition
						function checkProp(value, adapt, path, i){
							var l;
							path += path ? typeof i == 'number' ? '[' + i + ']' : typeof i == 'undefined' ? '' : '.' + i : i;
							function addError(message){
								errors[errors.length] = {property : path, message : message};
							}
							if((typeof adapt != 'object' || adapt instanceof Array) && (path || typeof adapt != 'function') && !(adapt && getType(adapt))){
								if(typeof adapt == 'function'){
									if(!(value instanceof adapt)){
										//addError("is not an instance of the class/constructor " + adapt.name);
										addError(validateUtil['Error_adapt_not_instance'].replace(/~~1/,adapt.name));
									}
								}else if(adapt){
									//addError("Invalid adapt/property definition " + adapt);
									addError(validateUtil['Error_adapt_invalid_property'].replace(/~~1/,adapt));
								}
								return null;
							}
							if(adapt['extends']){ // if it extends another schema, it must pass that schema as well
								checkProp(value,adapt['extends'],path,i);
							}
							// validate a value against a type definition
							function checkType(type,value){
								if(type){
									if(typeof type == 'string' && type != 'any' &&(type == 'null' ? value !== null : typeof value != type) &&
									!(value instanceof Array && type == 'array') && !(value instanceof Date && type == 'date') &&
									!(type == 'integer' && value % 1 === 0)){

										//return [{property:path,message:(typeof value) + " value found, but a " + type + " is required"}];
										return [{property:path,message: validateUtil['Error_adapt_field_type'].replace(/~~1/,(typeof value)).replace(/~~2/,type)}];
									}
									if(type instanceof Array){
										var unionErrors = [];
										for(var j = 0; j < type.length; j++){ // a union type
											if(!(unionErrors=checkType(type[j],value)).length){
												break;
											}
										}
										if(unionErrors.length){
											return unionErrors;
										}
									}else if(typeof type == 'object'){
										var priorErrors = errors;
										errors = [];
										checkProp(value,type,path);
										var theseErrors = errors;
										errors = priorErrors;
										//clear the tmp
										priorErrors = null;
										return theseErrors;
									}
								}
								return [];
							}
							if(value === undefined){
								if(adapt.required){
									//addError("is missing and it is required");
									addError(validateUtil['Error_adapt_required']);
								}
							}else{
								errors = errors.concat(checkType(getType(adapt),value));
								if(value != null){
									if(value instanceof Array){
										if(adapt.items){
											var itemsIsArray = adapt.items instanceof Array;
											var propDef = adapt.items;
											for (i = 0, l = value.length; i < l; i += 1) {
												if(itemsIsArray){
													propDef = adapt.items[i];
												}
												errors.concat(checkProp(value[i],propDef,path,i));
											}
										}
									}else if(adapt.properties || adapt.additionalProperties){
										errors.concat(checkObj(value, adapt.properties, path, adapt.additionalProperties));
									}
									if(adapt.pattern && typeof value == 'string' && !value.match(adapt.pattern)){
										//addError("does not match the regex pattern " + adapt.pattern);
										addError(validateUtil['Error_adapt_pattern'].replace(/~~1/,adapt.pattern));
									}
									if(adapt.maxLength && typeof value == 'string' && value.length > adapt.maxLength){
										//addError("may only be " + adapt.maxLength + " characters long");
										addError(validateUtil['Error_adapt_maxlength'].replace(/~~1/,adapt.maxLength));
									}
									if(adapt.minLength && typeof value == 'string' && value.length < adapt.minLength){
										//addError("must be at least " + adapt.minLength + " characters long");
										addError(validateUtil['Error_adapt_minlength'].replace(/~~1/,adapt.minLength));
									}
									if(typeof adapt.minimum !== undefined && typeof value == typeof adapt.minimum &&
									adapt.minimum > value){
										//addError("must have a minimum value of " + adapt.minimum);
										addError(validateUtil['Error_adapt_minimum'].replace(/~~1/,adapt.minimum));
									}
									if(typeof adapt.maximum !== undefined && typeof value == typeof adapt.maximum &&
									adapt.maximum < value){
										//addError("must have a maximum value of " + adapt.maximum);
										addError(validateUtil['Error_adapt_maximum'].replace(/~~1/,adapt.maximum));
									}
									if(adapt['enum']){
										var enumer = adapt['enum'];
										l = enumer.length;
										var found;
										for(var j = 0; j < l; j++){
											if(enumer[j]===value){
												found=1;
												break;
											}
										}
										if(!found){
											//addError("does not have a value in the enumeration " + enumer.join(", "));
											addError(validateUtil['Error_adapt_enumeration'].replace(/~~1/,enumer.join(", ")));
										}
									}
									if(typeof adapt.maxDecimal == 'number' &&
									(value.toString().match(new RegExp("\\.[0-9]{" + (schema.maxDecimal + 1) + ",}")))){
										//addError("may only have " + adapt.maxDecimal + " digits of decimal places");
										addError(validateUtil['Error_adapt_digits'].replace(/~~1/,adapt.maxDecimal));
									}
								}
							}
							return null;
						}
						//validate an object against an adapt
						function checkObj(instance,objTypeDef,path,additionalProp){
							if(typeof objTypeDef =='object'){
								if(typeof instance != 'object' || instance instanceof Array){
									//errors.push({property:path,message:"an object is required"});
									errors.push({property:path,message:validateUtil['Error_adapt_object_required']});
								}
								for(var i in objTypeDef){
									if(objTypeDef.hasOwnProperty(i)){
										var value = instance[i];
										// skip _not_ specified properties
										if (value === undefined){
											if(objTypeDef[i].required){
												//errors.push({property: i,message:"is missing and it is required"});
												errors.push({property: (path + "." + i),message:validateUtil['Error_adapt_required']});
											}
											continue;
										}
										//
										var propDef = objTypeDef[i];
										// set default
										if(value === undefined && propDef["default"]){
											value = instance[i] = propDef["default"];
										}
										//replace() to avoid XSS attack
										if(value && typeof value === "string"){
											value = value.replace(/<\/?\s*script\s*[^>]*>/gi, "");
											instance[i] = value;
										}
										checkProp(value,propDef,path,i);
									}
								}
							}
							for(i in instance){
								if(instance.hasOwnProperty(i) && !(i.charAt(0) == '_' && i.charAt(1) == '_') && objTypeDef && !objTypeDef[i] && additionalProp===false){
									//errors.push({property:path,message:(typeof value) + "The property " + i +
									//	" is not defined in the adapt and the adapt does not allow additional properties"});
									errors.push({property:path,message:validateUtil['Error_adapt_additional'].replace(/~~1/,(typeof value)).replace(/~~2/,i)});
								}
								if(!objTypeDef.hasOwnProperty(i)){
									//filter the object
									delete instance[i];
								}
								var requires = objTypeDef && objTypeDef[i] && objTypeDef[i].requires;
								if(requires && !(requires in instance)){
									//errors.push({property:path,message:"the presence of the property " + i + " requires that " + requires + " also be present"});
									errors.push({property:path,message:validateUtil['Error_adapt_presence'].replace(/~~1/,i).replace(/~~2/,requires)});
								}
								value = instance[i];
								if(additionalProp && (!(objTypeDef && typeof objTypeDef == 'object') || !(i in objTypeDef))){
									checkProp(value,additionalProp,path,i);
								}
								if(!_changing && value && value.$schema){
									errors = errors.concat(checkProp(value,value.$schema,path,i));
								}
							}
							return errors;
						}
						if(adapt){
							checkProp(obj,adapt,'', _changing || '');
						}
						if(!_changing && obj && obj.$schema){
							checkProp(obj,obj.$schema,'','');
						}
						return {valid:!errors.length,errors:errors};
					};
				})(jQuery,Hsbc);
				//plus
				$.fn.parsed = function(callback){
					$(this).live('pageParsed',function(data,page){
						callback(data,page);
					});
				}
				//
				$.fn.topic = function(inst,t){
					$(this).each(function(){
						var callbacks, topic = t && Hsbc.topics[this.id + "/" + t];
						if(!topic){
							callbacks = $.Callbacks();
							topic = {
								publish : callbacks.fire,
								subscribe : callbacks.add,
								unsubscribe : callbacks.remove,
								iabl : inst
							};
							if(t){
								Hsbc.topics[this.id + "/" + t] = topic;
							}
						}
					});
				}
				$.topic = function(iable,e){
					var callbacks, topic = e && Hsbc.topics[e];
					if(!topic){
						callbacks = $.Callbacks();
						topic = {
							publish : callbacks.fire,
							subscribe : callbacks.add,
							unsubscribe : callbacks.remove,
							iabl : iabl
						};
						if(e){
							Hsbc.topics[e] = topic;
						}
					}
				}
				//
				// Expose hsbc mobile as an AMD module, but only for AMD loaders.
				if (typeof define === "function" && define.amd && define.amd.Hsbc) {
					define("hsbc", [],
					function() {
						return Hsbc.mobile;
					});
				}
			})(window);